package com.haojiang.mh.service.ble

import android.bluetooth.BluetoothGatt
import android.util.Log
import com.clj.fastble.BleManager
import com.clj.fastble.callback.BleGattCallback
import com.clj.fastble.callback.BleNotifyCallback
import com.clj.fastble.callback.BleScanCallback
import com.clj.fastble.callback.BleWriteCallback
import com.clj.fastble.data.BleDevice
import com.clj.fastble.exception.BleException
import com.clj.fastble.utils.HexUtil
import com.google.gson.Gson
import com.haojiang.mh.cache.Const
import com.haojiang.mh.service.entity.BleNewData
import com.haojiang.mh.service.entity.DeviceAttr
import com.haojiang.mh.service.entity.DeviceWifiBean
import com.haojiang.mh.service.event.OneEvent
import com.haojiang.mh.utils.CrcUtil
import com.haojiang.mh.utils.KLog
import com.haojiang.mh.utils.MUtil
import com.haojiang.mh.utils.SpUtil
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.ArrayBlockingQueue

/**
 * Ble操作管理
 * SpUtil[Const.DEVICE_MAC, ""]决定是否有设备
 * currDevice决定设备是否连接（在线/离线）
 *
 * 设备配置逻辑：搜索设备列表-->选择设备(此时已连接)-->配置WiFi(配置成功保存设备Mac地址)-->选择单双人模式-->进入主界面使用
 *
 * 消息订阅失败之后尝试重试,三次重试失败则视为设备离线
 *
 * Created by ZapFive on 2019-05-25.
 *
 * wuzhuang@mirahome.me
 */
class BleHandle {

    companion object {
        val instance: BleHandle by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { BleHandle() }
    }

    var currDevice: BleDevice? = null //当前连接设备
    private var spliceSize = 0 //拼接协议的总长度
    private var spliceDataList = ArrayList<Byte>() //拼接协议缓存
    private var syncHeartbeatJob: Job? = null // 发送心跳Job
    private var sendUpdateFirmwareJob: Job? = null //发起固件升级通知
    private var delayWriteDataJob: Job? = null //持续占据资源，300ms执行一次数据写入
    private var delayAddNotifyJob: Job? = null //尝试3次添加订阅
    private val heartbeatProtocol = HexUtil.hexStringToBytes("FA025E01")
    private var deviceAttr: DeviceAttr? = null //设备属性
    var bleNewData: BleNewData? = null //当前最新的实时数据
    var deviceWifiBean: DeviceWifiBean? = null //配置的WiFi信息
    private val writeDataQueue = ArrayBlockingQueue<ByteArray>(1024)//发送数据队列
    var isCancelUpdate = false //点击取消升级做记录，再次进入app重新提示
    private var retryAddNotifyCount = 0//尝试订阅次数

    //region 设备扫描-连接-断开-删除
    /**
     * 开启扫描
     */
    fun scanDevice(callback: BleScanCallback) {
        BleManager.getInstance().scan(callback)
    }

    /**
     * 取消扫描
     */
    fun cancelScan() {
        BleManager.getInstance().cancelScan()
    }

    /**
     * 连接设备
     */
    fun connectDevice(isUseMacConnect: Boolean = false) {
        KLog.d("-------connectDevice")
        GlobalScope.launch {
            //连接之前断开所有已连接，保持单个设备连接状态
            BleManager.getInstance().disconnectAllDevice()
            delay(300)
            if (isUseMacConnect) {
                BleManager.getInstance().connect(SpUtil[Const.DEVICE_MAC, ""], bleGattCallback)
            } else {
                BleManager.getInstance().connect(currDevice, bleGattCallback)
            }
        }
    }

    /**
     * 设备连接回调
     */
    private val bleGattCallback = object : BleGattCallback() {

        override fun onStartConnect() {
            KLog.d("onStartConnect  DEVICE_MAC：${SpUtil[Const.DEVICE_MAC, ""]}")
        }

        override fun onDisConnected(
            isActiveDisConnected: Boolean,//手动断开连接
            device: BleDevice?,
            gatt: BluetoothGatt?,
            status: Int
        ) {
            KLog.d("isActiveDisConnected:$isActiveDisConnected status:$status")
            if (!isActiveDisConnected) {//非手动断开需要重置本地状态
                onDeviceDisconnect()
            }
            EventBus.getDefault().post(OneEvent(Const.DISCONNECT_DEVICE))
        }

        override fun onConnectSuccess(bleDevice: BleDevice?, gatt: BluetoothGatt?, status: Int) {
            BleHandle.instance.currDevice = bleDevice
            if (SpUtil[Const.DEVICE_MAC, ""] != bleDevice?.mac) {
                SpUtil.put(Const.TOKEN_LOCAL, "")
                SpUtil.put(Const.TOKEN_EXIST_TIME, -1L)
            }
            EventBus.getDefault().post(OneEvent(Const.CONNECT_DEVICE, Const.OK_RESULT))//通知连接成功
            startDelayWriteDataJob()//开启周期轮询写入操作
            addNotify()//添加订阅
        }

        override fun onConnectFail(bleDevice: BleDevice?, exception: BleException?) {
            KLog.e("onConnectFail  ${bleDevice?.mac} $exception")
            EventBus.getDefault().post(OneEvent(Const.CONNECT_DEVICE, Const.NO_RESULT))//通知连接失败
        }
    }

    /**
     * 断开连接 手动触发
     */
    fun disConnectDevice(isOperateAll: Boolean = false) {
        KLog.d("-------disConnectDevice")
        if (isOperateAll) {
            BleManager.getInstance().disconnectAllDevice()
        } else {
            BleManager.getInstance().disconnect(currDevice)
        }
    }

    /**
     * 删除当前设备 手动触发
     */
    fun deleteDevice() {
        if (isConnected()) {
            disConnectDevice()
        }
        onDeviceDisconnect(true)
    }
    //endregion

    //region 公共方法
    /**
     * 新版本
     */
    fun isNeedToUpdate(): Boolean {
        if (isCancelUpdate) return false
        if (!isConnected()) return false
        if (deviceAttr == null) return false
        if (deviceAttr?.isNewVersion() != true) return false
        return true
    }

    /**
     * 新版本号
     */
    fun getNewVersion(): String {
        return deviceAttr?.NV ?: ""
    }

    /**
     * 设备是否是单人模式，不可执行切换模式
     */
    fun isSingleModel(): Boolean {
        return deviceAttr?.SDMode == 1
    }

    /**
     * App退出之前
     */
    fun onMainDestroy() {
        if (!isConnected()) return
        disConnectDevice(true)
        onDeviceDisconnect()
    }

    /**
     * 当设备断开连接  重置所有状态
     */
    fun onDeviceDisconnect(isNeedClearDeviceInfo: Boolean = false) {
        MUtil.cancelJob(syncHeartbeatJob)//停止心跳
        stopWriteDataJob()//停止发送数据
        stopNotify()//停止订阅
        currDevice = null
        deviceAttr = null
        isCancelUpdate = false
        if (isNeedClearDeviceInfo) {
            SpUtil.clearDeviceLocalInfo()
        }
    }

    /**
     * 当前是否已连接
     */
    fun isConnected(): Boolean {
        KLog.d("-------isConnected")
        return currDevice != null && BleManager.getInstance().isConnected(currDevice)
    }

    /**
     * 当前设备已连接且在线，可以操控
     */
    fun isCanOperateDevice(): Boolean {
        return SpUtil[Const.DEVICE_MAC, ""].isNotEmpty() && isConnected()
    }

    /**
     * is support ble?
     */
    fun isSupportBle(): Boolean {
        return BleManager.getInstance().isSupportBle
    }

    /**
     * judge Bluetooth is enable
     */
    fun isBleEnable(): Boolean {
        return BleManager.getInstance().isBlueEnable
    }
    //endregion

    //region 读写指令集合

    /**
     * 执行写入,单独线程，间隔300s一次
     */
    private fun startDelayWriteDataJob() {
        stopWriteDataJob()
        delayWriteDataJob = GlobalScope.launch {
            while (isActive) {
                if (writeDataQueue.isNotEmpty()) {
                    doWrite(writeDataQueue.take())
                }
                delay(300L)
            }
        }
    }

    /**
     * 停止写数据
     */
    private fun stopWriteDataJob() {
        MUtil.cancelJob(delayWriteDataJob)
        writeDataQueue.clear()
    }

    /**
     * 添加需要发送的数据到队列中
     */
    private fun addWriteDataToQueue(data: ByteArray) {
        writeDataQueue.put(data)
    }

    /**
     * 执行写入
     */
    private fun doWrite(writeData: ByteArray) {
        BleManager.getInstance().write(
            currDevice,
            Const.SERVICE_UUID,
            Const.CHARACTER_UUID,
            writeData,
            object : BleWriteCallback() {
                override fun onWriteSuccess(current: Int, total: Int, justWrite: ByteArray?) {
                    KLog.d("current:$current total:$total justWrite:${HexUtil.encodeHexStr(justWrite)}")
                    EventBus.getDefault().post(OneEvent(Const.WRITE_CALLBACK, Const.OK_RESULT))
                }

                override fun onWriteFailure(exception: BleException?) {
                    KLog.e("心跳发送失败:${exception.toString()}")
                    EventBus.getDefault().post(OneEvent(Const.WRITE_CALLBACK, Const.NO_RESULT))
                }
            })
    }

    /**
     * 组装协议
     */
    private fun buildUpProtocol(type: MsgType, content: String): ByteArray {
        val lenHex = intToHex(content.length / 2 + 2)
        val crcTxt = "${byteToHex(type.code)}$content"
        val ptr = HexUtil.hexStringToBytes(crcTxt)
        val crcHex = CrcUtil.calcCheck(ptr, ptr.size)
        return HexUtil.hexStringToBytes("FA$lenHex${byteToHex(crcHex)}$crcTxt")
    }

    /**
     * 同步时间  当前时间==>小端16进制串
     */
    private fun replySyncTime() {
        val timeHex = (System.currentTimeMillis() / 1000).toString(16)
        var result = ""
        while (result.length < timeHex.length) {
            result = "${timeHex[result.length]}${timeHex[result.length + 1]}$result"
        }
        addWriteDataToQueue(buildUpProtocol(MsgType.SYNC_TIME, result))
    }

    /**
     * 升级固件 3次重试
     */
    fun sendUpdateFirmware() {
        var sendCount = 0
        MUtil.cancelJob(sendUpdateFirmwareJob)
        sendUpdateFirmwareJob = GlobalScope.launch {
            while (isActive) {
                if (sendCount < 3) {
                    sendCount++
                    addWriteDataToQueue(
                        buildUpProtocol(MsgType.UPDATE_FIRMWARE, byteToHex(UpdateType.UPDATE_START.code))
                    )
                    delay(500L)
                } else {
                    MUtil.cancelJob(sendUpdateFirmwareJob)
                }
            }
        }
    }

    /**
     * app收到联网消息后，发送回执
     */
    fun sendCallBackInfo(){
        addWriteDataToQueue(buildUpProtocol(MsgType.OPERATE_REPLY, byteToHex(MsgType.SET_WIFI_INFO.code)))
    }


    /**
     * 配置WiFi
     */
    fun sendWifiInfo() {
        if (deviceWifiBean == null) {
            EventBus.getDefault().post(OneEvent(Const.SET_WIFI_EVENT, -1))
            return
        }
        val content = HexUtil.encodeHexStr(Gson().toJson(deviceWifiBean).toByteArray())
        Log.e("colin", deviceWifiBean.toString() + "==" + HexUtil.encodeHexStr(Gson().toJson(deviceWifiBean).toByteArray()))
        Log.e("colin", deviceWifiBean.toString() + "==" + HexUtil.encodeHexStr(Gson().toJson(deviceWifiBean).toByteArray()))
        val data = buildUpProtocol(MsgType.SET_WIFI_INFO, content)
        addWriteDataToQueue(data)
    }

    /**
     * 控制设备单双人模式
     */
    fun sendModelOperate(isChecked: Boolean) {
        val content = "0103${if (isChecked) "02" else "01"}"
        val data = buildUpProtocol(MsgType.DEVICE_OPERATE, content)
        addWriteDataToQueue(data)
    }

    /**
     * 控制设备打鼾干预
     */
    fun sendInterveneOperate(isLeft: Boolean, isChecked: Boolean) {
        val content = "01${if (isLeft) "01" else "02"}${if (isChecked) "01" else "00"}"
        val data = buildUpProtocol(MsgType.DEVICE_OPERATE, content)
        addWriteDataToQueue(data)
    }

    /**
     * 收到设备属性之后 发送回执
     */
    private fun replyDeviceAttr() {
        addWriteDataToQueue(buildUpProtocol(MsgType.OPERATE_REPLY, byteToHex(MsgType.SYNC_DEVICE_ATTR.code)))
    }
    //endregion

    //region 订阅--协议解析

    /**
     * 添加消息订阅
     */
    fun addNotify() {
        if (currDevice == null) return
        delayAddNotify()
    }

    /**
     * 2秒一次尝试订阅
     */
    private fun delayAddNotify() {
        MUtil.cancelJob(delayAddNotifyJob)
        delayAddNotifyJob = GlobalScope.launch {
            if (retryAddNotifyCount >= 3) {
                MUtil.cancelJob(delayAddNotifyJob)
                //超过3次断开连接
                BleManager.getInstance().disconnect(currDevice)
                onDeviceDisconnect()
            } else {
                delay(1_000L)
                retryAddNotifyCount++
                KLog.w("notify retryAddNotifyCount $retryAddNotifyCount")
                BleManager.getInstance().notify(currDevice, Const.SERVICE_UUID, Const.CHARACTER_UUID, bleNotifyCallback)
            }
        }
    }

    /**
     * 订阅回调
     */
    private val bleNotifyCallback = object : BleNotifyCallback() {
        override fun onCharacteristicChanged(data: ByteArray?) {
            if (data == null || data.isEmpty()) return
            KLog.d("head: ${byteToHex(data[0])}  data: ${HexUtil.encodeHexStr(data)}")
            dataAnalyze(data)
        }

        override fun onNotifyFailure(exception: BleException?) {
            KLog.e("onNotifyFailure: $exception")
            delayAddNotify()
        }

        override fun onNotifySuccess() {
            KLog.d("onNotifySuccess")
            MUtil.cancelJob(delayAddNotifyJob)
            retryAddNotifyCount = 0
            handleHeartbeatPeriod()//发送心跳
        }
    }

    /**
     * 当前协议头是FA则开始判定长度和CRC
     * 如果头部不是FA，当前拼接协议列表为空则直接丢弃，列表不为空则继续追加
     */
    fun dataAnalyze(data: ByteArray) {
        if (isLegalProtocolHead(data)) {
            //协议长度最少为4
            if (data.size < 4) return

            //当前重置组装数据相关参数
            spliceSize = 0
            if (spliceDataList.isNotEmpty()) spliceDataList.clear()

            val len = data[1].toInt()
            if (len > 18) {
                spliceSize = len + 2
                spliceDataList.addAll(data.toList())
                return
            }
            protocolAnalyze(data)
        } else if (spliceSize > 0) {
            spliceDataList.addAll(data.toList())
            if (spliceDataList.size >= spliceSize) {
                spliceSize = 0
                val array = spliceDataList.toByteArray()
                spliceDataList.clear()
                protocolAnalyze(array)
            }
        }
    }

    /**
     * 解析协议
     */
    private fun protocolAnalyze(data: ByteArray) {
        val crc = data[2]
        val type = data[3]
        val afterCrc = data.copyOfRange(3, data.size)
        //校验码不通过直接丢弃
        if (crc != CrcUtil.calcCheck(afterCrc, afterCrc.size)) return
        when (type) {
            MsgType.SYNC_TIME.code -> replySyncTime()
            MsgType.REAL_TIME_DATA.code -> {
                val content = data.copyOfRange(4, data.size)
                bleNewData = Gson().fromJson(String(content).trim(), BleNewData::class.javaObjectType)
                if (bleNewData != null && SpUtil[Const.DEVICE_MAC, ""].isNotEmpty()) {
                    EventBus.getDefault().post(bleNewData)
//                    if (bleNewData?.WS == 0) {
//                        EventBus.getDefault().post(OneEvent(Const.DEVICE_WIFI_OFFLINE))
//                    }
                }
            }
            MsgType.SYNC_DEVICE_ATTR.code -> {
                replyDeviceAttr()
                val content = data.copyOfRange(4, data.size)
                deviceAttr = Gson().fromJson(String(content), DeviceAttr::class.javaObjectType)
                if (deviceAttr == null) return
                KLog.w("最新DeviceAttr：  " + deviceAttr.toString())
                SpUtil.put(Const.DEVICE_WIFI_MAC, deviceAttr?.MAC ?: "")
                SpUtil.put(Const.ORIGIN_DOUBLE_MODE, deviceAttr?.SDMode == 2)
                SpUtil.put(Const.IS_DOUBLE_MODE, deviceAttr?.SDModeSet == 2)
                SpUtil.put(Const.IS_LEFT_INTERVENE, deviceAttr?.LI == 1)
                SpUtil.put(Const.IS_RIGHT_INTERVENE, deviceAttr?.RI == 1)
            }
            MsgType.UPDATE_FIRMWARE.code -> {
                if (data.size != 6) return
                when (data[4]) {
                    UpdateType.UPDATE_PROGRESS.code -> {
                        MUtil.cancelJob(sendUpdateFirmwareJob)
                        EventBus.getDefault().post(OneEvent(Const.UPDATE_PROGRESS, data[5].toInt()))
                    }
                    UpdateType.UPDATE_RESULT.code -> {
                        EventBus.getDefault().post(OneEvent(Const.UPDATE_RESULT, data[5].toInt()))
                    }
                }
            }
            MsgType.DEVICE_OPERATE.code -> {
            }
            MsgType.SET_WIFI_INFO.code -> {
                if (data.size != 5) return
                //WiFi配置成功才算有设备
                SpUtil.put(Const.DEVICE_MAC, currDevice?.mac ?: "")
                EventBus.getDefault().post(OneEvent(Const.SET_WIFI_EVENT, data[4].toInt()))
            }
            MsgType.OPERATE_REPLY.code -> {
                if (data[4] == MsgType.SET_WIFI_INFO.code) {
                    EventBus.getDefault().post(OneEvent(Const.RECEIVE_CALLBACK))
                } else {
                    if (data[4] != MsgType.DEVICE_OPERATE.code) return
                    if (data.size != 7) return
                    when (data[5]) {
                        OperateType.LEFT_INTERVENE.code -> {
                            EventBus.getDefault().post(OneEvent(Const.LEFT_INTERVENE, data[6].toInt()))
                        }
                        OperateType.RIGHT_INTERVENE.code -> {
                            EventBus.getDefault().post(OneEvent(Const.RIGHT_INTERVENE, data[6].toInt()))
                        }
                        OperateType.USER_MODE.code -> {
                            EventBus.getDefault().post(OneEvent(Const.USER_MODE, data[6].toInt()))
                        }
                    }
                }
            }
            MsgType.NET_STATUS.code -> {
                if (data.size != 5) return
                val status = data[4].toInt()
                if (status == 0) {
                    EventBus.getDefault().post(OneEvent(Const.DEVICE_WIFI_OFFLINE))
                }
            }
        }
    }

    /**
     * 确认当前协议头是否是FA
     */
    private fun isLegalProtocolHead(data: ByteArray): Boolean {
        return "FA" == byteToHex(data[0])
    }

    /**
     * 停止订阅
     */
    private fun stopNotify() {
        BleManager.getInstance().stopNotify(currDevice, Const.SERVICE_UUID, Const.CHARACTER_UUID)
    }

    //endregion

    //region 5s 发一次心跳包
    fun handleHeartbeatPeriod() {
        MUtil.cancelJob(syncHeartbeatJob)
        syncHeartbeatJob = GlobalScope.launch {
            while (isActive) {
                addWriteDataToQueue(heartbeatProtocol)
                delay(5_000L)
            }
        }
    }
    //endregion

    //region 工具
    /**
     * Byte转Hex
     */
    private fun byteToHex(b: Byte): String {
        return intToHex(b.toInt())
    }

    /**
     * Int转Hex
     */
    private fun intToHex(i: Int): String {
        var hex = Integer.toHexString(i and 0xFF)
        if (hex.length < 2) {
            hex = "0$hex"
        }
        return hex.toUpperCase()
    }
    //endregion
}